package aof

import (
	"JoRedis/config"
	"JoRedis/interface/database"
	"JoRedis/lib/logger"
	"JoRedis/protocol"
	"JoRedis/redis"
	"JoRedis/util"
	"io"
	"os"
	"strconv"
)

type AofPayload struct {
	cmdBytesSlc [][]byte
	dbIndex     int
}

type AofHandler struct {
	// AOF初始化加载数据到DB中
	databasePtr database.DataBaseItf
	// AOF文件
	aofFilePtr *os.File
	// 添加AOF记录通道
	aofPayloadPtrChn chan *AofPayload
	// AOF文件名
	aofFileName string
}

func MakeAofHandler(databasePtr database.DataBaseItf) (*AofHandler, error) {
	aofHandlerPtr := &AofHandler{databasePtr: databasePtr}
	aofHandlerPtr.aofFileName = config.PropPtr.AppendFilename

	// 加载AOF文件中的日志到DB中
	originAppendOnly := config.PropPtr.AppendOnly
	// 避免在加载AOF文件数据时又再次写入AOF文件中
	config.PropPtr.AppendOnly = false
	// 加载AOF文件中的日志入DB中
	aofHandlerPtr.LoadAof()
	// AOF文件加载完后赋回原始值(开启AOF写入)
	config.PropPtr.AppendOnly = originAppendOnly

	filePtr, err := os.OpenFile(aofHandlerPtr.aofFileName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0600)
	if err != nil {
		return nil, err
	}
	aofHandlerPtr.aofFilePtr = filePtr
	aofHandlerPtr.aofPayloadPtrChn = make(chan *AofPayload)
	// 启动AOF文件数据写入
	go aofHandlerPtr.WriteAof()

	return aofHandlerPtr, nil
}

// AddAof 添加AOF记录
func (this *AofHandler) AddAof(dbIndex int, cmdBytesSlc [][]byte) {
	if config.PropPtr.AppendOnly && this.aofPayloadPtrChn != nil {
		this.aofPayloadPtrChn <- &AofPayload{
			cmdBytesSlc: cmdBytesSlc,
			dbIndex:     dbIndex,
		}
	}
}

// WriteAof 从AofPayloadPtrChn中读取数据并写入AOF文件中
func (this *AofHandler) WriteAof() {
	var curDBIndex = 0
	// 遍历添加AOF记录通道 -- 写入AOF件
	for aofPayloadPtr := range this.aofPayloadPtrChn {
		if curDBIndex != aofPayloadPtr.dbIndex {
			// DB索引变更
			selectCmdBytesSls := util.StrToBytesSlc("select", strconv.Itoa(aofPayloadPtr.dbIndex))
			mBulkReplyPtr := protocol.MakeMultiBulkReply(selectCmdBytesSls)
			// DB变更写入AOF
			_, _ = this.aofFilePtr.Write(mBulkReplyPtr.ToBytes())
			curDBIndex = aofPayloadPtr.dbIndex
		}
		// 指令写入AOF文件中
		mBulkReplyPtr := protocol.MakeMultiBulkReply(aofPayloadPtr.cmdBytesSlc)
		_, _ = this.aofFilePtr.Write(mBulkReplyPtr.ToBytes())
	}
}

// LoadAof 加载AOF文件中的日志入DB中
func (this *AofHandler) LoadAof() {
	filePtr, err := os.Open(this.aofFileName)
	if err != nil {
		if _, ok := err.(*os.PathError); ok {
			logger.Error(err.Error())
			return
		}
		logger.Warn(err)
		return
	}
	defer filePtr.Close()
	// 读取AOF文件中的指令
	aofPayloadPtrChn := protocol.ParseProtocol(filePtr)
	fakeConnPtr := redis.MakeFakeConn()
	// 遍历AOF文件解析后的指令通道--解析后的数据写入DB中
	for payloadPtr := range aofPayloadPtrChn {

		if payloadPtr.Err != nil {
			if payloadPtr.Err == io.EOF {
				// 文件读取完毕--退出
				break
			}
			logger.Error(payloadPtr.Err)
			continue
		}

		if payloadPtr.DataReply == nil {
			logger.Error("empty payload")
			continue
		}

		multiBulkReplyPtr, ok := payloadPtr.DataReply.(*protocol.MultiBulkReply)
		if !ok {
			logger.Error("require multi bulk protocol")
			continue
		}
		// 数据加载至DB中
		this.databasePtr.Exec(fakeConnPtr, multiBulkReplyPtr.ArgBytesSlc)
	}
}

// Close 关闭AOF文件
func (this *AofHandler) Close() {
	if this.aofFilePtr != nil {
		_ = this.aofFilePtr.Close()
		// TODO finishChn
	}
	close(this.aofPayloadPtrChn)
}
